import cv2
import time
import queue
import threading
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
from utils.Logger import Logger
from config.window_settings import WindowConfig
from controllers.YOLOController import YOLOController
from controllers.CameraController import CameraController
from config.message_constants import MainWindowMessages,StatusMessages


class MainWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        # 初始化线程通信队列（生产者-消费者模型）
        self.raw_frame_queue = queue.Queue(maxsize=30)  # 原始帧缓冲队列（最多缓存30帧）
        self.detection_queue = queue.Queue(maxsize=30)  # 检测结果缓冲队列（最多缓存30帧）
        # 线程控制事件
        self.thread_control = {
            'video': threading.Event(), # 视频处理线程控制
            'detect': threading.Event() # 检测线程控制
        }

        # 窗口初始化
        self.state('zoomed')        # 启动时窗口最大化
        self.resizable(True, True)  # 禁止调整窗口大小
        self.minsize(1024,768)      # 设置最小窗口尺寸
        self._load_configuration()  # 加载样式配置
        self._create_widgets()      # 创建GUI组件
        self._setup_bindings()      # 设置事件绑定
        # 绑定窗口调整事件
        self.bind("<Configure>", self._on_window_resize)

        # 初始化日志记录器（多个控制器使用同一个日志记录器）
        self.logger = Logger(
            log_dir=WindowConfig.PATH['log_dir'], 
            gui_callback=self.update_log_display,    # 日志回调函数：用于更新GUI显示
            module=WindowConfig.MODULE_NAME['main']  # 设置日志检测器默认模块名称
        )
        # 摄像头控制器（带帧回调）
        self.camera_controller = CameraController(
            status_callback=self.handle_camera_status,
            frame_callback=self._enqueue_raw_frame,  # 新增帧回调（每帧自动入队）
            logger=self.logger,
            module=WindowConfig.MODULE_NAME['camera']  # 设置模块名称
        )
        # YOLO检测控制器（延迟初始化）
        self.yolo_controller = None

    def _on_window_resize(self, event):
        """窗口大小变化时的处理"""
        # 确保是主窗口变化而不是子组件变化
        if event.widget == self:
            # 更新视频显示
            if self.camera_controller.is_running:
                self._update_video_display(self.camera_controller.get_frame())

    def _enqueue_raw_frame(self, frame):
        """将摄像头帧存入原始队列供检测使用（非阻塞模式）"""
        try:
            if self.raw_frame_queue.full():
                # 队列已满则自动丢弃旧帧避免堆积
                self.raw_frame_queue.get_nowait()
            self.raw_frame_queue.put(frame.copy(), block=False) # 否则存入新帧
        except Exception as e:
            self.logger.error(MainWindowMessages.FRAME_QUEUE_ERROR.format(str(e)))

    def handle_camera_status(self, status_code):
        """处理摄像头硬件状态变更"""
        status_map = {
            "CAMERA_INIT": StatusMessages.CAMERA_INIT,
            "CAMERA_ACTIVE": StatusMessages.CAMERA_ACTIVE,
            "CAMERA_DISCONNECTED": StatusMessages.CAMERA_DISCONNECTED,
            "CAMERA_RESOLUTION_CHANGE": StatusMessages.CAMERA_RESOLUTION_CHANGE,
            "CAMERA_ERROR": StatusMessages.CAMERA_ERROR,
            "SYSTEM_ERROR": StatusMessages.SYSTEM_ERROR
        }
        self.update_status(status_map.get(status_code, StatusMessages.UNKNOWN_STATUS))
    
    def handle_detection_status(self, status_code):
        """处理检测器状态变更"""
        status_map = {
            "DETECTION_ACTIVE": StatusMessages.DETECTION_ACTIVE,
            "ANOMALY_FOUND": StatusMessages.ANOMALY_FOUND,
            "ANOMALY_PERSISTENT": StatusMessages.ANOMALY_PERSISTENT,
            "DETECTION_IDLE": StatusMessages.DETECTION_IDLE,
            "DETECTION_ERROR": StatusMessages.DETECTION_ERROR
        }
        self.update_status(status_map.get(status_code, StatusMessages.UNKNOWN_STATUS))

    def update_log_display(self, log_data: dict):
        """线程安全的日志更新"""
        def safe_update():
            item_id = self.log_tree.insert('', 'end', values=(
                log_data['time'], 
                log_data['level'], 
                log_data['message']
            ))
            # 自动滚动到底部
            self.log_tree.see(item_id)
        
            # 高亮显示日志
            if log_data['level'] == "ERROR":
                last_item = self.log_tree.get_children()[-1]
                self.log_tree.item(last_item, tags=('error',))
                self.log_tree.tag_configure('error', foreground='red')
            elif log_data['level'] == "WARNING":
                last_item = self.log_tree.get_children()[-1]
                self.log_tree.item(last_item, tags=('warning',))
                self.log_tree.tag_configure('warning', foreground='goldenrod')
        self.after(0, safe_update)  # 确保在主线程执行
        
    def _load_configuration(self):
        """加载所有窗口配置"""
        # 加载窗口基本配置
        self.title(WindowConfig.TITLE)
        self.configure(bg=WindowConfig.STYLE["BG_COLOR"])  # 设置背景色
        # 配置ttk组件样式
        style = ttk.Style()
        # 配置状态栏标签样式
        style.configure(
            'StatusBar.TLabel',
            background=WindowConfig.STYLE["status_bar"]["bg"],  # 状态栏背景色
            foreground=WindowConfig.STYLE["status_bar"]["fg"],  # 状态栏前景色
            font=WindowConfig.STYLE["status_bar"]["font"]   # 状态栏字体
        )
        
    def _create_widgets(self):
        """创建界面组件"""
        # ==================== 菜单栏 ====================
        self._build_menu_system()
        
        # ==================== 主容器 ====================
        self.main_frame = ttk.Frame(self)
        self.main_frame.pack(expand=True, fill=tk.BOTH, padx=5, pady=(2,5))
        
        # 配置网格比例 (5:2)
        self.main_frame.columnconfigure(0, weight=5, uniform="main_col")    # 视频区域
        self.main_frame.columnconfigure(1, weight=2, uniform="main_col")    # 日志区域
        self.main_frame.rowconfigure(0, weight=1)   # 单行布局，行高自适应
        
        # ==================== 视频区域 ====================
        self.video_container = ttk.LabelFrame(self.main_frame, text=MainWindowMessages.RVC) # 视频容器
        self.video_container.grid(row=0, column=0, sticky="nsew", padx=2, pady=2)   # 填充视频区域
        self.video_container.grid_propagate(False)  # 保持固定比例
        self.video_label = ttk.Label(self.video_container)  # 用于显示视频帧
        self.video_label.pack(expand=True, fill=tk.BOTH, padx=2, pady=2)    # 填充视频区域
        
        # ==================== 日志区域 ====================
        self.log_container = ttk.LabelFrame(self.main_frame, text=MainWindowMessages.LOG)   # 日志容器
        self.log_container.grid(row=0, column=1, sticky="nsew", padx=2, pady=2) # 填充日志区域
        self._create_log_treeview() # 创建日志Treeview
        
        # ==================== 状态栏 ====================
        self.status_bar = ttk.Label(
            self,
            text=StatusMessages.READY,
            anchor=tk.W,    # 文本左对齐
            style='StatusBar.TLabel'    # 使用预定义样式
        )
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, before=self.main_frame) # 填充状态栏区域

    def _create_log_treeview(self):
        """创建完全基于比例布局的日志树状视图"""
        # 主容器
        log_frame = ttk.Frame(self.log_container)
        log_frame.pack(expand=True, fill=tk.BOTH)

        # 网格布局配置
        log_frame.grid_rowconfigure(0, weight=1)
        log_frame.grid_rowconfigure(1, weight=0)
        log_frame.grid_columnconfigure(0, weight=1)
        log_frame.grid_columnconfigure(1, weight=0)

        # 列比例配置
        self.log_columns = {
            'time': {
                'text': '时间',
                'weight': 0.35,  # 占总宽20%
                'anchor': 'center',
                'stretch': False  # 禁止拉伸
            },
            'level': {
                'text': '级别',
                'weight': 0.1,  # 占15%
                'anchor': 'center',
                'stretch': False  # 禁止拉伸
            },
            'message': {
                'text': '消息',
                'weight': 0.65,  # 剩余65%
                'anchor': 'w',
                'stretch': True  # 允许拉伸
            }
        }

        # 创建Treeview组件
        self.log_tree = ttk.Treeview(
            log_frame,
            columns=tuple(self.log_columns.keys()),
            show='headings',
            selectmode='browse'
        )

        # 初始化列配置（保持不变）
        for col, config in self.log_columns.items():
            self.log_tree.heading(col, text=config['text'])
            self.log_tree.column(
                col,
                width=1,  # 初始宽度设为1px（会被后续计算覆盖）
                anchor=config['anchor'],
                stretch=True  # 关键配置
            )

        # 垂直滚动条配置
        v_scrollbar = ttk.Scrollbar(
            log_frame, 
            orient=tk.VERTICAL,
            command=self.log_tree.yview
        )
        self.log_tree.configure(yscrollcommand=v_scrollbar.set)
        
        # 新增水平滚动条
        h_scrollbar = ttk.Scrollbar(
            log_frame,
            orient=tk.HORIZONTAL,
            command=self.log_tree.xview
        )
        self.log_tree.configure(xscrollcommand=h_scrollbar.set)

        # 布局组件
        self.log_tree.grid(row=0, column=0, sticky="nsew")
        v_scrollbar.grid(row=0, column=1, sticky="ns")
        h_scrollbar.grid(row=1, column=0, sticky="ew")

        # 动态调整列宽
        def on_resize(event):
            """纯比例布局调整"""
            # 计算可用宽度（总宽 - 滚动条宽度 - 边框补偿）
            total_width = self.log_tree.winfo_width()
            scrollbar_width = v_scrollbar.winfo_width()
            usable_width = total_width - scrollbar_width - 4  # 4px边框补偿
            
            # 按比例分配宽度
            allocated = 0
            last_col = None
            
            # 前N-1列分配计算
            for col, config in list(self.log_columns.items())[:-1]:
                col_width = int(usable_width * config['weight'])
                self.log_tree.column(col, width=col_width)
                allocated += col_width
                last_col = col
            
            # 最后一列占剩余宽度（解决整数计算误差）
            if last_col:
                remaining = usable_width - allocated
                last_col_config = self.log_columns[list(self.log_columns.keys())[-1]]
                self.log_tree.column(
                    list(self.log_columns.keys())[-1], 
                    width=max(remaining, 1)  # 确保最小1px
                )

        # 绑定事件（保持不变）
        self.log_tree.bind("<Configure>", on_resize)
        log_frame.bind("<Configure>", lambda e: self.log_tree.event_generate("<Configure>"))

    def _build_menu_system(self):
        """根据配置构建菜单系统"""
        self.menubar = tk.Menu(self, font=WindowConfig.STYLE["menu_font"])
        
        for menu_label, items in WindowConfig.MENU_HIERARCHY.items():
            menu = self._create_menu(items)
            self.menubar.add_cascade(label=menu_label, menu=menu)
        
        self.configure(menu=self.menubar)

    def _create_menu(self, items):
        """递归创建菜单结构"""
        menu = tk.Menu(self.menubar, tearoff=0)
        
        for label, shortcut in items.items():
            if label == "---":
                menu.add_separator()
                continue
            
            if isinstance(shortcut, dict):
                submenu = self._create_menu(shortcut)
                menu.add_cascade(label=label, menu=submenu)
            else:
                self._add_menu_command(menu, label, shortcut)
        
        return menu

    def _add_menu_command(self, menu, label, shortcut):
        """添加菜单命令项"""
        accel_text = ""
        if shortcut:
            # 转换快捷键格式
            processed_shortcut = shortcut.replace('Ctrl', 'Control').replace('+', '-')
            accel_text = f" ({shortcut})"  # 显示原始快捷键格式
            
            # 生成符合Tkinter要求的事件字符串
            key_sequence = f"<{processed_shortcut}>"
            
            menu.add_command(
                label=f"{label}{accel_text}",
                command=self._get_command_handler(label)
            )
        
        if shortcut:
            self.bind(key_sequence, self._get_command_handler(label))

    def _get_command_handler(self, menu_label):
        """获取菜单命令处理函数"""
        handlers = {
            "打开摄像头": self.open_camera,
            "关闭摄像头": self.close_camera,
            "开始检测": self.start_detection,
            "停止检测": self.stop_detection,
            "关于": self.show_about,
            "退出": self.quit_application
        }
        return handlers.get(menu_label, lambda: None)

    def _setup_bindings(self):
        """设置通用事件绑定"""
        self.protocol("WM_DELETE_WINDOW", self.quit_application)

    def update_status(self, message):
        """更新状态栏"""
        self.status_bar.config(text=message)
        self.update_idletasks()


    def _start_video_pipeline(self):
        """启动视频处理线程（双线程架构）"""
        # 启动检测线程（消费者）
        if not self.thread_control['detect'].is_set():
            self.thread_control['detect'].clear()
            detect_thread = threading.Thread(
                target=self._detection_worker,
                daemon=True # 守护线程（随主线程退出）
            )
            detect_thread.start()
            
        # UI更新循环（定时器驱动）
        self._schedule_ui_update()

    def _detection_worker(self):
        """持续处理原始帧队列"""
        while not self.thread_control['detect'].is_set():
            try:
                # 空值检查
                if not self.yolo_controller or not self.yolo_controller.is_running:
                    time.sleep(0.5)
                    continue
                    
                # 带超时的队列获取（防止永久阻塞）
                raw_frame = self.raw_frame_queue.get(timeout=1.0)

                # 执行YOLO检测
                processed_frame, has_anomaly, report = self.yolo_controller.detect(raw_frame)
                
                # 异常处理（示例）
                if has_anomaly:
                    self.logger.warning(MainWindowMessages.ANOMALY_WARNING.format(report))
                
                # 结果存入检测队列
                self.detection_queue.put((processed_frame, has_anomaly))
                
            except queue.Empty:
                continue    # 忽略空队列异常
            except Exception as e:
                self.logger.error(MainWindowMessages.DETECTION_THREAD_ERROR(str(e)))
                self.thread_control['detect'].set() # 终止线程

    def _schedule_ui_update(self):
        """动态调整刷新率的显示更新（30-60FPS）"""
        if not self.camera_controller.is_running:
            return
            
        try:
            # 优先显示检测结果
            processed_frame, has_anomaly = self.detection_queue.get_nowait()
            self._update_video_display(processed_frame)
            
            # 异常状态提示
            if has_anomaly:
                self.status_bar.config(foreground='red')
            else:
                self.status_bar.config(foreground=WindowConfig.STYLE["status_bar"]["fg"])
        except queue.Empty:
            # 降级显示原始帧（保持实时性）
            raw_frame = self.camera_controller.get_frame()
            if raw_frame is not None:
                self._update_video_display(raw_frame)
        finally:
            # 动态延迟计算（根据队列负载）
            delay = 20 if self.detection_queue.qsize() > 1 else 40  # 20ms或40ms
            self.after(delay, self._schedule_ui_update) # Tkinter定时器

    def _update_video_display(self, frame):
        """使用PIL更新视频标签"""
        try:
            if frame is not None:
                # 转换为PIL兼容的RGB格式
                img = Image.fromarray(frame)            # 创建PIL图像
                imgtk = ImageTk.PhotoImage(image=img)   # 转为Tk兼容格式

                # 更新显示组件
                self.video_label.config(image=imgtk)    # 设置新图像
                self.video_label.imgtk = imgtk          # 防止垃圾回收

        except Exception as e:
            self.logger.error(MainWindowMessages.DISPLAY_UPDATE_ERROR(str(e)))

    def _update_video_display(self, frame):
        """使用PIL更新视频标签，并调整尺寸填充视频区域"""
        try:
            if frame is not None:
                # 获取视频容器的当前尺寸
                container_width = self.video_container.winfo_width()
                container_height = self.video_container.winfo_height()
                
                # 确保容器尺寸有效（避免初始化为0）
                if container_width <= 1 or container_height <= 1:
                    return
                
                # 计算保持宽高比的缩放尺寸
                h, w = frame.shape[:2]
                ratio = min(container_width / w, container_height / h)
                new_w = int(w * ratio)
                new_h = int(h * ratio)
                
                # 缩放图像
                resized = cv2.resize(frame, (new_w, new_h))
                
                # 转换为PIL兼容的RGB格式
                img = Image.fromarray(resized)
                
                # 创建填充背景的图像（确保完全填充容器）
                bg = Image.new('RGB', (container_width, container_height), (0, 0, 0))
                offset_x = (container_width - new_w) // 2
                offset_y = (container_height - new_h) // 2
                bg.paste(img, (offset_x, offset_y))
                
                imgtk = ImageTk.PhotoImage(image=bg)
                
                # 更新显示组件
                self.video_label.config(image=imgtk)
                self.video_label.imgtk = imgtk

        except Exception as e:
            self.logger.error(MainWindowMessages.DISPLAY_UPDATE_ERROR(str(e)))

    # 以下是具体业务方法需要实现的部分
    def start_detection(self):
        """安全启动 YOLO 检测流程"""
        # 前置检查
        if not self.camera_controller.is_running:
            self.logger.warning(MainWindowMessages.CAMERA_NOT_RUNNING)
            return
        
        # 延迟初始化YOLO（节省资源）
        if self.yolo_controller is None:
            try:
                self.yolo_controller = YOLOController(
                    model_path=WindowConfig.PATH['model_path'],
                    status_callback=self.handle_detection_status,
                    logger=self.logger,
                    module=WindowConfig.MODULE_NAME['yolo'],    # 设置模块名称
                    anomaly_classes=['ngRAM']
                )
                # 模型有效性验证
                if not hasattr(self.yolo_controller, 'model') or self.yolo_controller.model is None:
                    raise RuntimeError(MainWindowMessages.MODEL_INIT_FAIL)

            except Exception as e:
                self.logger.error(MainWindowMessages.MODEL_LOAD_ERROR(str(e)))
                self.yolo_controller = None
                return

        # 启用检测器
        if not self.yolo_controller.is_running:
            self.yolo_controller.enable()
            
        # 启动处理流水线
        self._start_video_pipeline()

    def stop_detection(self):
        """完全释放模型资源"""
        if self.yolo_controller:
            self.yolo_controller.disable()
            del self.yolo_controller    # 主动释放资源
            self.yolo_controller = None # 清除引用
            
        self._clear_queues()    # 清空缓存帧
        self.update_status(StatusMessages.DETECTION_STOPPED)

    def show_about(self):
        """显示关于项目信息的窗口"""
        # 创建顶层窗口
        about_window = tk.Toplevel(self)
        about_window.title(MainWindowMessages.ABOUT)
        about_window.resizable(False, False)  # 禁止调整大小
        about_window.transient(self)  # 设置为模态窗口
        about_window.grab_set()  # 获取焦点
        
        # 设置窗口图标（如果有）
        try:
            about_window.iconbitmap(WindowConfig.PATH.get('icon', None))
        except:
            pass
        
        # 窗口内容框架
        content_frame = ttk.Frame(about_window, padding=20)
        content_frame.pack(fill=tk.BOTH, expand=True)
        
        # 项目标题
        title_label = ttk.Label(
            content_frame,
            text=WindowConfig.TITLE,
            font=("微软雅黑", 16, "bold"),
            foreground="#2c3e50"
        )
        title_label.pack(pady=(0, 10))
        
        # 版本信息
        version_label = ttk.Label(
            content_frame,
            text=f"版本: {WindowConfig.VERSION}",
            font=("微软雅黑", 12),
            foreground="#7f8c8d"
        )
        version_label.pack(pady=(0, 15))
        
        description_label = ttk.Label(
            content_frame,
            text=WindowConfig.DESCRIPTION,
            font=("微软雅黑", 11),
            foreground="#34495e",
            justify=tk.CENTER,
            wraplength=400
        )
        description_label.pack(pady=(0, 20))
        
        # 开发团队信息
        team_frame = ttk.LabelFrame(content_frame, text=MainWindowMessages.TEAM, padding=10)
        team_frame.pack(fill=tk.X, pady=(0, 20))

        # 获取团队成员列表
        team_members = WindowConfig.TEAM_NUMBERS  
        
        for member in team_members:
            member_label = ttk.Label(
                team_frame,
                text=member,
                font=("微软雅黑", 10),
                anchor=tk.W
            )
            member_label.pack(fill=tk.X, padx=5, pady=2)
        
        # 版权信息
        copyright_label = ttk.Label(
            content_frame,
            text=f"© {time.strftime('%Y')} {WindowConfig.COMPANY} 版权所有",
            font=("微软雅黑", 9),
            foreground="#95a5a6"
        )
        copyright_label.pack(pady=(10, 0))
        
        # 确定按钮
        button_frame = ttk.Frame(content_frame)
        button_frame.pack(fill=tk.X, pady=(10, 0))
        
        ok_button = ttk.Button(
            button_frame,
            text="确定",
            command=about_window.destroy,
            width=10
        )
        ok_button.pack(side=tk.RIGHT)
        
        # 居中窗口
        self._center_window(about_window, 500, 400)
        
        # 设置窗口关闭行为
        about_window.protocol("WM_DELETE_WINDOW", about_window.destroy)
        
        # 等待窗口关闭
        self.wait_window(about_window)

    def _center_window(self, window, width, height):
        """将窗口居中显示"""
        # 获取屏幕尺寸
        screen_width = window.winfo_screenwidth()
        screen_height = window.winfo_screenheight()
        
        # 计算位置
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        
        # 设置窗口位置
        window.geometry(f"{width}x{height}+{x}+{y}")

    def open_camera(self, event=None):
        """安全打开摄像头"""
        if self.camera_controller.is_running:
            return
            
        self.update_status(StatusMessages.CAMERA_INIT)
        try:
            if self.camera_controller.start_camera():
                # 启动视频处理流水线
                self.thread_control['video'].clear()
                self._start_video_pipeline()
                self.update_status(StatusMessages.CAMERA_ACTIVE)
            else:
                self.update_status(StatusMessages.CAMERA_DISCONNECTED)
        except Exception as e:
            self.logger.error(MainWindowMessages.CAMERA_HARDWARE_ERROR.format(str(e)))
            self.update_status(StatusMessages.CAMERA_ERROR)

    def close_camera(self):
        """安全关闭所有硬件资源"""
        # 停止检测功能
        self.stop_detection()
        
        # 停止摄像头
        if self.camera_controller.is_running:
            self.camera_controller.stop_camera()
            self.logger.info(MainWindowMessages.CAMERA_CLOSED)
            
        # 清空UI显示
        self.video_label.config(image='')
        self.update_status(StatusMessages.CAMERA_DISCONNECTED)
        
        # 重置状态
        self.thread_control['video'].set()
        self.thread_control['detect'].set()
        self._clear_queues()

    def _clear_queues(self):
        """清空线程通信队列（防止内存泄漏）"""
        for q in [self.raw_frame_queue, self.detection_queue]:
            while not q.empty():
                try:
                    q.get_nowait()  # 非阻塞获取
                except:
                    continue    # 忽略空队列异常
    
    def _clear_queues(self):
        """清空所有帧队列"""
        while not self.raw_frame_queue.empty():
            self.raw_frame_queue.get()
        while not self.detection_queue.empty():
            self.detection_queue.get()

    def quit_application(self):
        """安全退出应用"""
        # 停止所有硬件组件
        self.stop_detection()
        self.close_camera()
        
        # 显式释放YOLO资源
        if self.yolo_controller:
            del self.yolo_controller
            self.yolo_controller = None
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
        
        # 清空队列
        self._clear_queues()
        self.destroy()